window.onload = loadScene;

/**
 * canvas 元素id
 */
const canvasElementId = "c";

/**
 * canvas 宽高
 */
let canvasWidth = window.innerWidth;
let canvasHeight = window.innerHeight;

/**
 * 线条数量
 */
let lineNum = 40000;
/**
 * setup 类型
 */
let setupPath = 2;

/**
 * draw 类型
 */
let drawType = 1;

let numLines = 40000;
let count = 0;
let cn = 0;

/**
 * canvas 上下文
 */
let canvasContext;

/**
 * 顶点们
 */
let vertices = [];

let velThetaArr;
let velRadArr;
let ratio;
let velocities;
let thetaArr;
let freqArr;
let boldRateArr;

// let colorLoc;
// const target = [];
const randomTargetXArr = [], randomTargetYArr = [];

// ===================================
//    初始化 WebGL & 生成 3D scene
// ===================================
function loadScene() {

  /**
   * 创建一个 WebGLRenderingContext 对象作为3D渲染的上下文
   */
  const canvas = document.getElementById(canvasElementId);
  canvasContext = canvas.getContext("experimental-webgl");
  if (!canvasContext) {
    console.log("no available WebGL context ！");
    return;
  }
  //设置 canvas 的宽高 (等于浏览器宽高)
  canvas.width = canvasWidth;
  canvas.height = canvasHeight;
  canvasContext.viewport(0, 0, canvas.width, canvas.height);

  /**
   * 创建着色器
   */
  const vertexShaderScript = document.getElementById("shader-vs");
  const vertexShader = canvasContext.createShader(canvasContext.VERTEX_SHADER);
  canvasContext.shaderSource(vertexShader, vertexShaderScript.text);
  canvasContext.compileShader(vertexShader);
  if (!canvasContext.getShaderParameter(vertexShader, canvasContext.COMPILE_STATUS)) {
    console.log("Couldn't compile the vertex shader");
    canvasContext.deleteShader(vertexShader);
    return;
  }
  //再创建一个着色器
  const fragmentShaderScript = document.getElementById("shader-fs");
  const fragmentShader = canvasContext.createShader(canvasContext.FRAGMENT_SHADER);
  canvasContext.shaderSource(fragmentShader, fragmentShaderScript.text);
  canvasContext.compileShader(fragmentShader);
  if (!canvasContext.getShaderParameter(fragmentShader, canvasContext.COMPILE_STATUS)) {
    console.log("Couldn't compile the fragment shader");
    canvasContext.deleteShader(fragmentShader);
    return;
  }
  //创建一个着色器程序,将两个着色器放进程序，并链接canvas
  canvasContext.program = canvasContext.createProgram();
  canvasContext.attachShader(canvasContext.program, vertexShader);
  canvasContext.attachShader(canvasContext.program, fragmentShader);
  canvasContext.linkProgram(canvasContext.program);
  if (!canvasContext.getProgramParameter(canvasContext.program, canvasContext.LINK_STATUS)) {
    console.log("Unable to initialise shaders");
    canvasContext.deleteProgram(canvasContext.program);
    canvasContext.deleteProgram(vertexShader);
    canvasContext.deleteProgram(fragmentShader);
    return;
  }
  
  //安装程序作为当前渲染状态的一部分
  canvasContext.useProgram(canvasContext.program);
  //从链接的着色器程序中获取 vertexPosition 属性
  const vertexPosition = canvasContext.getAttribLocation(canvasContext.program, "vertexPosition");
  //启用 vertexPosition 顶点属性数组
  //如果启用，阵列当调用 canvasContext.drawArrays、canvasContext.drawElements 等命令时，将被访问用于渲染
  canvasContext.enableVertexAttribArray(vertexPosition);
  //清除指定颜色的颜色缓冲区(r, g, b, a)
  canvasContext.clearColor(0.0, 0.0, 0.0, 1.0);
  //清除深度缓冲区。 指定的值被限制在 [0,1] 范围内
  canvasContext.clearDepth(1.0);
  
  //    Enable depth testing. This is a technique used for hidden surface removal.启用深度测试。 这是一种用于去除隐藏表面的技术。
  //    It assigns a value (z) to each pixel that represents the distance from this
  //    pixel to the viewer. When another pixel is drawn at the same location the z
  //    values are compared in order to determine which pixel should be drawn.
  //canvasContext.enable(canvasContext.DEPTH_TEST);

  canvasContext.enable(canvasContext.BLEND);
  canvasContext.disable(canvasContext.DEPTH_TEST);
  canvasContext.blendFunc(canvasContext.SRC_ALPHA, canvasContext.ONE);

  //    Specify which function to use for depth buffer comparisons. It compares the
  //    value of the incoming pixel against the one stored in the depth buffer.
  //    Possible values are (from the OpenGL documentation):
  //    GL_NEVER - Never passes.
  //    GL_LESS - Passes if the incoming depth value is less than the stored depth value.
  //    GL_EQUAL - Passes if the incoming depth value is equal to the stored depth value.
  //    GL_LEQUAL - Passes if the incoming depth value is less than or equal to the stored depth value.
  //    GL_GREATER - Passes if the incoming depth value is greater than the stored depth value.
  //    GL_NOTEQUAL - Passes if the incoming depth value is not equal to the stored depth value.
  //    GL_GEQUAL - Passes if the incoming depth value is greater than or equal to the stored depth value.
  //    GL_ALWAYS - Always passes.
  //canvasContext.depthFunc(canvasContext.LEQUAL);

  //创建一个图形 
  //首先创建一个顶点缓冲区，我们可以在其中存储数据
  const vertexBuffer = canvasContext.createBuffer();
  //将缓冲区对象绑定到 ARRAY_BUFFER 目标
  canvasContext.bindBuffer(canvasContext.ARRAY_BUFFER, vertexBuffer);

  setup();

  vertices = new Float32Array(vertices);
  velocities = new Float32Array(velocities);
  thetaArr = new Float32Array(thetaArr);
  velThetaArr = new Float32Array(velThetaArr);
  velRadArr = new Float32Array(velRadArr);

  //    Creates a new data store for the vertices array which is bound to the ARRAY_BUFFER.
  //    The third paramater indicates the usage pattern of the data store. Possible values are
  //    (from the OpenGL documentation):
  //    The frequency of access may be one of these:
  //    STREAM - The data store contents will be modified once and used at most a few times.
  //    STATIC - The data store contents will be modified once and used many times.
  //    DYNAMIC - The data store contents will be modified repeatedly and used many times.
  //    The nature of access may be one of these:
  //    DRAW - The data store contents are modified by the application, and used as the source for
  //           GL drawing and image specification commands.
  //    READ - The data store contents are modified by reading data from the GL, and used to return
  //           that data when queried by the application.
  //    COPY - The data store contents are modified by reading data from the GL, and used as the source
  //           for GL drawing and image specification commands.
  canvasContext.bufferData(canvasContext.ARRAY_BUFFER, vertices, canvasContext.DYNAMIC_DRAW);

  //    Clear the color buffer and the depth buffer
  canvasContext.clear(canvasContext.COLOR_BUFFER_BIT | canvasContext.DEPTH_BUFFER_BIT);

  //    Define the viewing frustum parameters
  //    More info: http://en.wikipedia.org/wiki/Viewing_frustum
  //    More info: https://knol.google.com/k/view-frustum
  const fieldOfView = 30.0;
  const aspectRatio = canvas.width / canvas.height;
  const nearPlane = 1.0;
  const farPlane = 10000.0;
  const top = nearPlane * Math.tan(fieldOfView * Math.PI / 360.0);
  const bottom = -top;
  const right = top * aspectRatio;
  const left = -right;

  //     Create the perspective matrix. The OpenGL function that's normally used for this,
  //     glFrustum() is not included in the WebGL API. That's why we have to do it manually here.
  //     More info: http://www.cs.utk.edu/~vose/c-stuff/opengl/glFrustum.html
  const a = (right + left) / (right - left);
  const b = (top + bottom) / (top - bottom);
  const c = (farPlane + nearPlane) / (farPlane - nearPlane);
  const d = (2 * farPlane * nearPlane) / (farPlane - nearPlane);
  const x = (2 * nearPlane) / (right - left);
  const y = (2 * nearPlane) / (top - bottom);
  const perspectiveMatrix = [
    x, 0, a, 0,
    0, y, b, 0,
    0, 0, c, d,
    0, 0, -1, 0
  ];

  //     Create the modelview matrix
  //     More info about the modelview matrix: http://3dengine.org/Modelview_matrix
  //     More info about the identity matrix: http://en.wikipedia.org/wiki/Identity_matrix
  const modelViewMatrix = [
    1, 0, 0, 0,
    0, 1, 0, 0,
    0, 0, 1, 0,
    0, 0, 0, 1
  ];
  //     Get the vertex position attribute location from the shader program
  const vertexPosAttribLocation = canvasContext.getAttribLocation(canvasContext.program, "vertexPosition");
  //				colorLoc = canvasContext.getVaryingLocation(canvasContext.program, "vColor");
  //				console.log("color loc : " + colorLoc );
  //     Specify the location and format of the vertex position attribute
  canvasContext.vertexAttribPointer(vertexPosAttribLocation, 3.0, canvasContext.FLOAT, false, 0, 0);
  //canvasContext.vertexAttribPointer(colorLoc, 4.0, canvasContext.FLOAT, false, 0, 0);
  //     Get the location of the "modelViewMatrix" uniform variable from the
  //     shader program
  const uModelViewMatrix = canvasContext.getUniformLocation(canvasContext.program, "modelViewMatrix");
  //     Get the location of the "perspectiveMatrix" uniform variable from the
  //     shader program
  const uPerspectiveMatrix = canvasContext.getUniformLocation(canvasContext.program, "perspectiveMatrix");
  //     Set the values
  canvasContext.uniformMatrix4fv(uModelViewMatrix, false, new Float32Array(perspectiveMatrix));
  canvasContext.uniformMatrix4fv(uPerspectiveMatrix, false, new Float32Array(modelViewMatrix));
  //	canvasContext.varyingVector4fv(
  //     Draw the triangles in the vertex buffer. The first parameter specifies what
  //     drawing mode to use. This can be GL_POINTS, GL_LINE_STRIP, GL_LINE_LOOP,
  //     GL_LINES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN, GL_TRIANGLES, GL_QUAD_STRIP,
  //     GL_QUADS, and GL_POLYGON
  //canvasContext.drawArrays( canvasContext.LINES, 0, numLines );
  //canvasContext.flush();

  //setInterval( drawScene, 1000 / 40 );
  animate();
  setTimeout(timer, 10000)
}
function timer() {
  drawType = (drawType + 1) % 3;
  setTimeout(timer, 10000);
}

// =================================
//
// =================================
function animate() {
  requestAnimationFrame(animate);
  draw();
  canvasContext.lineWidth(1);
  canvasContext.bufferData(canvasContext.ARRAY_BUFFER, vertices, canvasContext.DYNAMIC_DRAW);
  canvasContext.clear(canvasContext.COLOR_BUFFER_BIT | canvasContext.DEPTH_BUFFER_BIT);
  //canvasContext.drawArrays( canvasContext.LINES_STRIP, 0, numLines );
  canvasContext.drawArrays(canvasContext.LINES, 0, numLines);
  //canvasContext.drawArrays( canvasContext.QUAD_STRIP, 0, numLines );
  canvasContext.flush();
}

// ===================================
function setup() {
  switch (setupPath) {
    case 1 : {
      setup1();
      break;
    }
    case 2 : {
      setup2();
      break;
    }
  }
}
// ===================================
function setup1() {
  velThetaArr = [];
  velRadArr = [];
  ratio = canvasWidth / canvasHeight;
  velocities = [];
  
  for (var i = 0; i < numLines; i++) {
    var rad = Math.random() * 2 + .5;
    var theta = Math.random() * Math.PI * 2;
    var velTheta = Math.random() * Math.PI * 2;

    vertices.push(rad * Math.cos(theta), rad * Math.sin(theta), 1.83);//(Math.random() * 2 - 1)*ratio, Math.random() * 2 - 1, 1.83 );
    vertices.push(rad * Math.cos(theta), rad * Math.sin(theta), 1.83);

    velocities.push((Math.random() * 2 - 1) * .05, (Math.random() * 2 - 1) * .05, .93 + Math.random() * .02);
    velThetaArr.push(velTheta);
    velRadArr.push(rad);
  }
}
// -------------------------------
function setup2() {

  velThetaArr = [];
  velRadArr = [];
  ratio = canvasWidth / canvasHeight;
  velocities = [];
  thetaArr = [];
  freqArr = [];
  boldRateArr = [];

  for (let j = 0; j < lineNum; j++) {
    const rad = (0.1 + .2 * Math.random()); //随机浮点数
    const theta = Math.random() * Math.PI * 2; //Π*2
    const velTheta = Math.random() * Math.PI * 2 / 30;
    const freq = Math.random() * 0.12 + 0.03;
    const boldRate = Math.random() * .04 + .01;
    const randomPosX = (Math.random() * 2 - 1) * window.innerWidth / window.innerHeight;
    const randomPosY = Math.random() * 2 - 1;

    vertices.push(rad * Math.cos(theta), rad * Math.sin(theta), 1.83);
    vertices.push(rad * Math.cos(theta), rad * Math.sin(theta), 1.83);

    thetaArr.push(theta);
    velThetaArr.push(velTheta);
    velRadArr.push(rad);
    freqArr.push(freq);
    boldRateArr.push(boldRate);

    randomTargetXArr.push(randomPosX);
    randomTargetYArr.push(randomPosY);
  }
  freqArr = new Float32Array(freqArr);
}
// -------------------------------


// ===================================
function draw() {
  switch (drawType) {
    case 0:
      draw0();
      break;
    case 1:
      draw1();
      break;
    case 2:
      draw2();
      break;
  }
}
function draw0() {
  let i, n = vertices.length, p, bp;
  let px, py;
  let pTheta;
  let rad;
  let num;
  let targetX, targetY;

  for (i = 0; i < numLines * 2; i += 2) {
    count += .3;
    bp = i * 3;

    vertices[bp] = vertices[bp + 3];
    vertices[bp + 1] = vertices[bp + 4];

    num = parseInt(i / 2);
    targetX = randomTargetXArr[num];
    targetY = randomTargetYArr[num];


    px = vertices[bp + 3];
    px += (targetX - px) * (Math.random() * .04 + .06);
    vertices[bp + 3] = px;


    //py = (Math.sin(cn) + 1) * .2 * (Math.random() * .5 - .25);
    py = vertices[bp + 4];
    py += (targetY - py) * (Math.random() * .04 + .06);
    vertices[bp + 4] = py;

  }
}
function draw1() {

  let i, n = vertices.length, p, bp;
  let px, py;
  let pTheta;
  let rad;
  let num;
  let targetX, targetY;

  for (i = 0; i < numLines * 2; i += 2) {
    count += .3;
    bp = i * 3;

    vertices[bp] = vertices[bp + 3];
    vertices[bp + 1] = vertices[bp + 4];

    num = parseInt(i / 2);
    pTheta = thetaArr[num];
    rad = velRadArr[num];

    pTheta = pTheta + velThetaArr[num];
    thetaArr[num] = pTheta;

    targetX = rad * Math.cos(pTheta);
    targetY = rad * Math.sin(pTheta);

    px = vertices[bp + 3];
    px += (targetX - px) * (Math.random() * .1 + .1);
    vertices[bp + 3] = px;


    //py = (Math.sin(cn) + 1) * .2 * (Math.random() * .5 - .25);
    py = vertices[bp + 4];
    py += (targetY - py) * (Math.random() * .1 + .1);
    vertices[bp + 4] = py;
  }
}
function draw2() {
  cn += .1;

  let i, n = vertices.length, p, bp;
  let px, py;
  let pTheta;
  let rad;
  let num;

  for (i = 0; i < numLines * 2; i += 2) {
    count += .3;
    bp = i * 3;
    // copy old positions

    vertices[bp] = vertices[bp + 3];
    vertices[bp + 1] = vertices[bp + 4];

    num = parseInt(i / 2);
    pTheta = thetaArr[num];

    rad = velRadArr[num];// + Math.cos(pTheta + i * freqArr[i]) *  boldRateArr[num];

    pTheta = pTheta + velThetaArr[num];
    thetaArr[num] = pTheta;

    px = vertices[bp + 3];
    px = rad * Math.cos(pTheta) * 0.1 + px;
    vertices[bp + 3] = px;


    //py = (Math.sin(cn) + 1) * .2 * (Math.random() * .5 - .25);
    py = vertices[bp + 4];

    py = py + rad * Math.sin(pTheta) * 0.1;
    //p *= ( Math.random() -.5);
    vertices[bp + 4] = py;
  }
}
